#include "src/pbc/transform/portable-transformer-base.h"

#include "include/v8-version.h"

namespace v8 {
namespace internal {
using namespace portable;

Handle<FixedArray> PbcTransformerBase::GetOrTransformTopDeclarations() {
  if (!top_declarations_.is_null()) {
    return top_declarations_;
  }
  uint32_t length = pbc_script_.NumberOfTopDeclarations();
  Handle<FixedArray> data =
      isolate_.factory()->NewFixedArray(length, AllocationType::kOld);
  for (uint32_t i = 0; i < length; ++i) {
    // TODO:(wwq) VariableDeclaration, FunctionDeclaration...
    StringRef str_ref = pbc_script_.GetTopDeclaration(i);
    Handle<String> str =
        UtilsInternalizeStringRef(pbc_script_, isolate_, str_ref);
    data->set(i, *str);
  }
  top_declarations_ = data;
  return data;
}

template <typename T>
Handle<ObjectBoilerplateDescription> PbcTransformerBase::ToV8ObjectLiteral(
    T object_literal) {
  ObjectDescript desc = object_literal.GetDescrit();
  Handle<ObjectBoilerplateDescription> boilerplate =
      isolate_.factory()->NewObjectBoilerplateDescription(
          desc.boilerplate_properties_, desc.property_length_, desc.index_keys_,
          desc.has_seen_proto_);

  // set properties one by one
  uint32_t num_props = object_literal.NumberOfProperties();
  for (uint32_t i = 0; i < num_props; ++i) {
    if constexpr (std::is_same_v<T, PbcDefaultObjectLiteral>) {
      if (object_literal.IsShortValue()) {
        PbcShortProperty property = object_literal.GetShortProperty(i);
        Handle<Object> key = PbcShortValueToV8Value(property.key_);
        Handle<Object> value = PbcShortValueToV8Value(property.value_);
        boilerplate->set_key_value(i, *key, *value);
        continue;
      }
    }
    PbcProperty property = object_literal.GetProperty(i);
    Handle<Object> key = PbcValueToV8Value(property.key_);
    Handle<Object> value = PbcValueToV8Value(property.value_);
    boilerplate->set_key_value(i, *key, *value);
  }
  boilerplate->set_flags(desc.boilerplate_description_flag_);
  return boilerplate;
}

Handle<TemplateObjectDescription> PbcTransformerBase::ToV8TemplateObject(
    PbcTemplateLiteral template_literal) {
  Handle<FixedArray> raw_strings_handle = isolate_.factory()->NewFixedArray(
      template_literal.Length(), AllocationType::kOld);
  bool raw_and_cooked_match = template_literal.IsDuplicated();
#if V8_MAJOR_VERSION == 11
  {
    DisallowGarbageCollection no_gc;
    FixedArray raw_strings = *raw_strings_handle;

    for (int i = 0; i < raw_strings.length(); ++i) {
      raw_strings.set(i, *PbcValueToV8Value(template_literal.GetRawString(i)));
    }
  }
  Handle<FixedArray> cooked_strings_handle = raw_strings_handle;
  if (!raw_and_cooked_match) {
    cooked_strings_handle = isolate_.factory()->NewFixedArray(
        template_literal.Length(), AllocationType::kOld);
    DisallowGarbageCollection no_gc;
    FixedArray cooked_strings = *cooked_strings_handle;
    for (int i = 0; i < cooked_strings.length(); ++i) {
      cooked_strings.set(
          i, *PbcValueToV8Value(template_literal.GetCookedString(i)));
    }
  }
#else
  {
    DisallowGarbageCollection no_gc;
    Tagged<FixedArray> raw_strings = *raw_strings_handle;

    for (int i = 0; i < raw_strings->length(); ++i) {
      raw_strings->set(i, *PbcValueToV8Value(template_literal.GetRawString(i)));
    }
  }
  Handle<FixedArray> cooked_strings_handle = raw_strings_handle;
  if (!raw_and_cooked_match) {
    cooked_strings_handle = isolate_.factory()->NewFixedArray(
        template_literal.Length(), AllocationType::kOld);
    DisallowGarbageCollection no_gc;
    Tagged<FixedArray> cooked_strings = *cooked_strings_handle;
    for (int i = 0; i < cooked_strings->length(); ++i) {
      cooked_strings->set(
          i, *PbcValueToV8Value(template_literal.GetCookedString(i)));
    }
  }
#endif

  return isolate_.factory()->NewTemplateObjectDescription(
      raw_strings_handle, cooked_strings_handle);
}

Handle<ArrayBoilerplateDescription> PbcTransformerBase::ToV8ArrayLiteral(
    PbcArrayLiteral array_literal) {
  ElementsKind kind = static_cast<ElementsKind>(array_literal.kind());
  bool use_doubles = IsDoubleElementsKind(kind);
  uint32_t num_elements = array_literal.NumberOfElements();

  Handle<FixedArrayBase> elements;
  if (use_doubles) {
    elements = isolate_.factory()->NewFixedDoubleArray(num_elements,
                                                       AllocationType::kOld);
  } else {
    elements = isolate_.factory()->NewFixedArrayWithHoles(num_elements,
                                                          AllocationType::kOld);
  }

  for (uint32_t i = 0; i < num_elements; ++i) {
    if (use_doubles) {
      PbcValue elem = array_literal.GetElement(i);
      if (elem.Kind() == PbcValueKind::kUniqueValue) {
        UniqueTag tag = static_cast<const UniqueScalar&>(elem).Tag();
#if V8_MAJOR_VERSION == 11
        if (tag == UniqueTag::kTagTheHole) {
          DCHECK(IsHoleyElementsKind(kind));
          FixedDoubleArray::cast(*elements).set_the_hole(i);
        } else if (tag == UniqueTag::kTagUninitialized) {
          FixedDoubleArray::cast(*elements).set(i, 0);
        }
#else
        if (tag == UniqueTag::kTagTheHole) {
          DCHECK(IsHoleyElementsKind(kind));
          Cast<FixedDoubleArray>(*elements)->set_the_hole(i);
        } else if (tag == UniqueTag::kTagUninitialized) {
          Cast<FixedDoubleArray>(*elements)->set(i, 0);
        }
#endif
      } else {
        DCHECK(elem.Kind() == PbcValueKind::kDouble ||
               elem.Kind() == PbcValueKind::kSmi);
        double number;
        if (elem.Kind() == PbcValueKind::kDouble) {
          number = pbc_script_.GetDouble(static_cast<const DoubleRef&>(elem));
        } else {
          number = static_cast<const SmiScalar&>(elem).GetValue();
        }
#if V8_MAJOR_VERSION == 11
        FixedDoubleArray::cast(*elements).set(i, number);
#else
        Cast<FixedDoubleArray>(*elements)->set(i, number);
#endif
      }
    } else {
      Handle<Object> value;
      if (array_literal.IsShortValue()) {
        PbcShortValue elem = array_literal.GetShortElement(i);
        if (elem.Kind() == PbcShortValueKind::kUniqueValue) {
          UniqueTag tag = static_cast<const PbcShortUnique&>(elem).Tag();
          if (tag == UniqueTag::kTagTheHole) {
            DCHECK(IsHoleyElementsKind(kind));
            continue;
          }
          if (tag == UniqueTag::kTagUninitialized) {
            value = handle(Smi::zero(), &isolate_);
          } else {
            value = PbcShortValueToV8Value(elem);
          }
        } else {
          value = PbcShortValueToV8Value(elem);
        }
      } else {
        PbcValue elem = array_literal.GetElement(i);
        if (elem.Kind() == PbcValueKind::kUniqueValue) {
          UniqueTag tag = static_cast<const UniqueScalar&>(elem).Tag();
          if (tag == UniqueTag::kTagTheHole) {
            DCHECK(IsHoleyElementsKind(kind));
            continue;
          }
          if (tag == UniqueTag::kTagUninitialized) {
            value = handle(Smi::zero(), &isolate_);
          } else {
            value = PbcValueToV8Value(elem);
          }
        } else {
          value = PbcValueToV8Value(elem);
        }
      }

      // We shouldn't allocate after creating the boilerplate value.
      DisallowGarbageCollection no_gc;
#if V8_MAJOR_VERSION == 11
      DCHECK_EQ(kind, GetMoreGeneralElementsKind(
                          kind, (*value).OptimalElementsKind(
                                    GetPtrComprCageBase(*elements))));
      FixedArray::cast(*elements).set(i, *value);
#else
      DCHECK_EQ(kind, GetMoreGeneralElementsKind(
                          kind, Object::OptimalElementsKind(
                                    *value, GetPtrComprCageBase(*elements))));
      Cast<FixedArray>(*elements)->set(i, *value);
#endif
    }
  }
  if (array_literal.is_cow()) {
#if V8_MAJOR_VERSION == 11
    elements->set_map_safe_transition(
        ReadOnlyRoots(&isolate_).fixed_cow_array_map());
#else
    elements->set_map_safe_transition(
        &isolate_, ReadOnlyRoots(&isolate_).fixed_cow_array_map(),
        kReleaseStore);
#endif
  }

  return isolate_.factory()->NewArrayBoilerplateDescription(kind, elements);
}

Handle<Object> PbcTransformerBase::PbcShortValueToV8Value(
    const PbcShortValue& entry) {
  Handle<Object> result;
  switch (entry.Kind()) {
    case PbcShortValueKind::kUniqueValue: {
      UniqueTag tag = static_cast<const PbcShortUnique&>(entry).Tag();
      switch (tag) {
        case kTagNull: {
          result = isolate_.factory()->null_value();
          break;
        }
        case kTagUndefined: {
          result = isolate_.factory()->undefined_value();
          break;
        }
        case kTagUninitialized: {
          result = isolate_.factory()->uninitialized_value();
          break;
        }
        case kTagTrue: {
          result = isolate_.factory()->true_value();
          break;
        }
        case kTagFalse: {
          result = isolate_.factory()->false_value();
          break;
        }
        case kTagTopDeclarations: {
          result = GetOrTransformTopDeclarations();
          break;
        }
        case kTagNaN: {
          result = isolate_.factory()->nan_value();
          break;
        }
        case kEmptyObjectBoilerplateDescription: {
          result = isolate_.factory()->empty_object_boilerplate_description();
          break;
        }
        case kTagTheHole: {
          result = isolate_.factory()->the_hole_value();
          break;
        }
        case kClassFieldsSymbol: {
          result = isolate_.factory()->class_fields_symbol();
          break;
        }
        default: {
          // NYI
          CHECK(false);
          break;
        }
      }
      break;
    }
    case PbcShortValueKind::kShortSmi: {
      result = handle(
          Smi::FromInt(static_cast<const PbcShortSmi&>(entry).GetValue()),
          &isolate_);
      break;
    }
    case PbcShortValueKind::kShortU8String: {
      AllowGarbageCollection allow_gc;
      std::string_view str =
          pbc_script_.GetString(static_cast<const PbcShortStringRef&>(entry));
      result = UtilsInternalizeString(isolate_, str);
      break;
    }
    case PbcShortValueKind::kShortU16String: {
      AllowGarbageCollection allow_gc;
      std::u16string_view str = pbc_script_.GetU16String(
          static_cast<const PbcShortStringRef&>(entry));
      result = UtilsInternalizeU16String(isolate_, str);
      break;
    }
    case PbcShortValueKind::kShortFunction: {
      uint32_t func_id =
          static_cast<const PbcShortFunction&>(entry).GetFuncId();
      result = GetOrCreateFuncData(func_id);
      break;
    }
    case PbcShortValueKind::kShortScope: {
      const PbcShortScope& scope_ref = static_cast<const PbcShortScope&>(entry);
      result = GetOrTransformScope(scope_ref.GetScopeId());
      break;
    }
    case PbcShortValueKind::kShortArrayLiteral: {
      PbcArrayLiteral array_literal = pbc_script_.GetArrayLiteral(
          static_cast<const PbcShortLiteralRef&>(entry));
      result = ToV8ArrayLiteral(array_literal);
      break;
    }
    case PbcShortValueKind::kShortObjectLiteral: {
      PbcDefaultObjectLiteral object_literal =
          pbc_script_.GetDefaultObjectLiteral(
              static_cast<const PbcShortLiteralRef&>(entry));
      result = ToV8ObjectLiteral(object_literal);
      break;
    }
    default:
      CHECK(false);
  }
  return result;
}

Handle<Object> PbcTransformerBase::PbcValueToV8Value(const PbcValue& entry) {
  Handle<Object> result;
  switch (entry.Kind()) {
    case PbcValueKind::kUniqueValue: {
      UniqueTag tag = static_cast<const UniqueScalar&>(entry).Tag();
      switch (tag) {
        case kTagNull: {
          result = isolate_.factory()->null_value();
          break;
        }
        case kTagUndefined: {
          result = isolate_.factory()->undefined_value();
          break;
        }
        case kTagUninitialized: {
          result = isolate_.factory()->uninitialized_value();
          break;
        }
        case kTagTrue: {
          result = isolate_.factory()->true_value();
          break;
        }
        case kTagFalse: {
          result = isolate_.factory()->false_value();
          break;
        }
        case kTagTopDeclarations: {
          result = GetOrTransformTopDeclarations();
          break;
        }
        case kTagNaN: {
          result = isolate_.factory()->nan_value();
          break;
        }
        case kEmptyObjectBoilerplateDescription: {
          result = isolate_.factory()->empty_object_boilerplate_description();
          break;
        }
        case kTagTheHole: {
          result = isolate_.factory()->the_hole_value();
          break;
        }
        case kClassFieldsSymbol: {
          result = isolate_.factory()->class_fields_symbol();
          break;
        }
        default: {
          // NYI
          CHECK(false);
          break;
        }
      }
      break;
    }
    case PbcValueKind::kSmi: {
      result =
          handle(Smi::FromInt(static_cast<const SmiScalar&>(entry).GetValue()),
                 &isolate_);
      break;
    }
    case PbcValueKind::kDouble: {
      double number =
          pbc_script_.GetDouble(static_cast<const DoubleRef&>(entry));
      result = isolate_.factory()->NewNumber<AllocationType::kOld>(number);
      break;
    }
    case PbcValueKind::kU8String: {
      AllowGarbageCollection allow_gc;
      std::string_view str =
          pbc_script_.GetString(static_cast<const StringRef&>(entry));
      result = UtilsInternalizeString(isolate_, str);
      break;
    }
    case PbcValueKind::kU16String: {
      AllowGarbageCollection allow_gc;
      std::u16string_view str =
          pbc_script_.GetU16String(static_cast<const StringRef&>(entry));
      result = UtilsInternalizeU16String(isolate_, str);
      break;
    }
    case PbcValueKind::kBigInt: {
      AllowGarbageCollection allow_gc;
      std::string_view bigint_str =
          pbc_script_.GetBigInt(static_cast<const BigIntRef&>(entry));
      result = StringToBigInt(&isolate_,
                              UtilsInternalizeString(isolate_, bigint_str))
                   .ToHandleChecked();
      break;
    }
    // TODO:(wwq) Handle all kinds of scope such as FUNCTION_SCOPE,
    // WITH_SCOPE...
    case PbcValueKind::kScope: {
      const ScopeRef& scope_ref = static_cast<const ScopeRef&>(entry);
      result = GetOrTransformScope(scope_ref.GetScopeId());
      break;
    }
    case PbcValueKind::kFunction: {
      uint32_t func_id = static_cast<const FunctionRef&>(entry).GetFuncId();
      result = GetOrCreateFuncData(func_id);
      break;
    }
    case PbcValueKind::kDefaultObjectLiteral: {
      PbcDefaultObjectLiteral object_literal =
          pbc_script_.GetDefaultObjectLiteral(
              static_cast<const LiteralRef&>(entry));
      result = ToV8ObjectLiteral(object_literal);
      break;
    }
    case PbcValueKind::kObjectLiteral: {
      PbcObjectLiteral object_literal =
          pbc_script_.GetObjectLiteral(static_cast<const LiteralRef&>(entry));
      result = ToV8ObjectLiteral(object_literal);
      break;
    }
    case PbcValueKind::kTemplateLiteral: {
      PbcTemplateLiteral template_literal =
          pbc_script_.GetTemplateLiteral(static_cast<const LiteralRef&>(entry));
      result = ToV8TemplateObject(template_literal);
      break;
    }
    case PbcValueKind::kArrayLiteral: {
      PbcArrayLiteral array_literal =
          pbc_script_.GetArrayLiteral(static_cast<const LiteralRef&>(entry));
      result = ToV8ArrayLiteral(array_literal);
      break;
    }
    case PbcValueKind::kClassLiteral: {
      PbcClassLiteral class_literal =
          pbc_script_.GetClassLiteral(static_cast<const LiteralRef&>(entry));
      auto class_boilerplate = ClassBoilerplate::New(
          &isolate_, pbc_script_, class_literal, AllocationType::kOld);
      result = class_boilerplate;
      break;
    }
    default: {
      CHECK(false);
      break;
    }
  }
  return result;
}

void PbcTransformerBase::initSharedFunctionInfo(
    uint32_t func_id, Handle<SharedFunctionInfo> sfi) {
  PbcFunction pbc_func = pbc_script_.GetFunction(func_id);
  CHECK(!script_.is_null());
#if V8_MAJOR_VERSION == 11
  sfi->set_script(*script_);
#else
  sfi->set_script(*script_, kReleaseStore);
#endif

  // set language mode
  auto language_mode = pbc_func.IsStrictMode() ? i::LanguageMode::kStrict
                                               : i::LanguageMode::kSloppy;
  // We only allow language mode transitions that set the same language mode
  // again or go up in the chain:
  if (sfi->language_mode() != i::LanguageMode::kStrict) {
    sfi->set_language_mode(language_mode);
  }

  // The scope_id in Function Scope is the same as func_id.
  uint32_t scope_id = func_id;
  Handle<ScopeInfo> scope_info = GetOrTransformScope(scope_id);
  sfi->SetScopeInfo(*scope_info);
  // todo:notice that function.ToString will use dummy_prefix_ as content
  scope_info->SetPositionInfo(0, static_cast<int>(sizeof(dummy_prefix_) - 1));
#ifdef OBJECT_PRINT
  if (i::v8_flags.log_pbc) {
    std::cout << "created scope info print:\n";
    scope_info->ScopeInfoPrint(std::cout);
  }
#endif

  // consistent with PbcSerializer::Run
  sfi->set_function_literal_id(func_id);
  sfi->set_internal_formal_parameter_count(pbc_func.GetParameterCount());
  sfi->set_expected_nof_properties(pbc_func.GetExpectedNofProperties());
  // TransformFunction should refer more to InitFromFunctionLiteral
  sfi->set_syntax_kind(
      static_cast<FunctionSyntaxKind>(pbc_func.GetFunctionSyntaxKind()));

  sfi->set_requires_instance_members_initializer(
      pbc_func.GetRequiresInstanceMembersInitializer());
}

}  // namespace internal
}  // namespace v8
